package com.llbnk.service.impl;

import com.llbnk.dao.ItemDOMapper;
import com.llbnk.dao.ItemStockDOMapper;
import com.llbnk.dao.StockLogDOMapper;
import com.llbnk.error.BusinessException;
import com.llbnk.error.EmBusinessError;
import com.llbnk.mq.MqProducer;
import com.llbnk.pojo.entity.ItemDO;
import com.llbnk.pojo.entity.ItemStockDO;
import com.llbnk.pojo.entity.StockLogDO;
import com.llbnk.pojo.model.ItemModel;
import com.llbnk.pojo.model.PromoModel;
import com.llbnk.service.ItemService;
import com.llbnk.validator.ValidationResult;
import com.llbnk.validator.ValidatorImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author llbnk
 * @create 2022-08-21 16:26
 */
@Service
@Slf4j
public class ItemServiceImpl implements ItemService {

    //在订单交易过程中，异步更新数据库
    @Resource
    private MqProducer mqProducer;

    @Resource
    private ItemDOMapper itemDOMapper;

    @Resource
    private ItemStockDOMapper itemStockDOMapper;

    @Resource
    private ValidatorImpl validator;

    @Resource
    private PromoServiceImpl promoService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private StockLogDOMapper stockLogDOMapper;


    /**
     * 将商品领域模型转为orm映射对象
     * @param itemModel 领域模型
     * @return 数据对象
     */
    private ItemDO convertItemDOFromItemModel(ItemModel itemModel){
        if(itemModel == null){
            return null;
        }
        ItemDO itemDO = new ItemDO();
        BeanUtils.copyProperties(itemModel, itemDO);
        //BeanUtils不会copy不同类型的属性，价格需要我们自己来
        itemDO.setPrice(itemModel.getPrice().doubleValue());
        itemDO.setSales(0);
        return itemDO;
    }

    /**
     * 将库存领域模型转为orm映射对象
     * @param itemModel 领域模型
     * @return 库存数据对象
     */
    private ItemStockDO convertItemStockDOFromItemModel(ItemModel itemModel){
        if(itemModel == null){
            return null;
        }
        ItemStockDO itemStockDO = new ItemStockDO();
        itemStockDO.setItemId(itemModel.getId());
        itemStockDO.setStock(itemModel.getStock());

        return itemStockDO;
    }

    @Override
    @Transactional
    public ItemModel createItem(ItemModel itemModel) throws BusinessException {
        //校验入参
        ValidationResult result = validator.validate(itemModel);
        if(result.isHasErrors()){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
        }
        //转化itemmodel -> entity
        ItemDO itemDO = convertItemDOFromItemModel(itemModel);

        //写入数据库
        System.out.println(itemDO.getId());
        itemDOMapper.insertSelective(itemDO);
        System.out.println(itemDO.getId());
        itemModel.setId(itemDO.getId());

        ItemStockDO itemStockDO = convertItemStockDOFromItemModel(itemModel);
        itemStockDOMapper.insertSelective(itemStockDO);
        //返回创建完成的对象
        return getItemById(itemModel.getId());
    }

    @Override
    public List<ItemModel> listItem() {
        List<ItemDO> itemDOList = itemDOMapper.listItem();
        List<ItemModel> itemModelList = itemDOList.stream().map(itemDO -> {
            ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
            ItemModel itemModel = convertModelFromEntity(itemDO, itemStockDO);
            return itemModel;
        }).collect(Collectors.toList());
        return itemModelList;
    }

    @Override
    public ItemModel getItemById(Integer id) {
        ItemDO itemDO = itemDOMapper.selectByPrimaryKey(id);
        if(itemDO == null){
            return null;
        }
        //操作获得库存
        ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());

        //将entity -> model
        ItemModel itemModel = convertModelFromEntity(itemDO, itemStockDO);

        //获取活动商品信息
        PromoModel promoModel = promoService.getPromoByItemId(itemModel.getId());
        //该商品有活动且活动未结束
        if(promoModel != null && promoModel.getStatus() != 3){
            itemModel.setPromoModel(promoModel);
        }

        return itemModel;
    }

    /**
     * 减库存
     * 1.先减去redis中的数量
     * 2.然后从rocketmq中同步更新数据库，
     * 不过同步更新数据库有些问题，就是回滚问题
     * Transactional标签在createOrder上有在decreaseStock但是调用的方法是使用外面大的Transactional
     * 所以传播方式是require_new这种方式
     * 的确出现异常可以回滚数据库，但是无法组织MQ发送消息回滚。
     * 比如订单模块出现错误，于是回滚操作了。但是因为MQ发送消息无法回滚。这样会存在stock数据库多扣库存情况发生。
     * 3.所以推荐使用异步更新库存方法如下面asyncDecreaseStock
     */
    @Override
    @Transactional
    public boolean decreaseStock(Integer itemId, Integer amount) {
            Long result = redisTemplate.opsForValue()
                    .increment("promo_item_stock_" + itemId, amount.intValue() * -1);
            if (result >= 0) { // > 变 >= 再分化
                //更新数据库 (采用同步方式更新mysql）

                //通过生产者发送更新mysql消息返回，如果在rocketmq的producer就出现异常了就返回失败，回滚redis中扣减的库存
                boolean mqResult = mqProducer.asyncReduceStock(itemId, amount);
                if(!mqResult){
                    redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
                }

                return true;
            }else {
                //库存为负，此次交易失败，补回redis中库存
                redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
                return false;
            }
    }



    @Override
    @Transactional
    public boolean asynchronousDecreaseStock(Integer itemId, Integer amount) {
        Long result = redisTemplate.opsForValue()
                .increment("promo_item_stock_" + itemId, amount.intValue() * -1);
        if (result >= 0) { // > 变 >= 再分化
            //更新数据库 (采用异步方式更新mysql）
            //交给asyncDecreaseStock去更新
            return true;
        }else {
            //库存为负，此次交易失败，补回redis中库存
            //redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
            asynchronousIncreaseStock(itemId,amount);
            return false;
        }
    }

    @Override
    @Transactional
    public boolean SoldOutAsynDecreaseStock(Integer itemId, Integer amount) {
        Long result = redisTemplate.opsForValue()
                .increment("promo_item_stock_" + itemId, amount.intValue() * -1);
        if (result > 0) {
            //更新数据库 (采用异步方式更新mysql）
            //交给asyncDecreaseStock去更新
            return true;
        }else if(result == 0){
            //增加了商品售空的信息！！！防止顶点流水表大量生成废流水
            //通过放在redis中的标志位来判断是否当前商品已经售罄
            //打上库存售罄的标识
            redisTemplate.opsForValue().set("promo_item_stock_invalid_"+itemId, "true");
            redisTemplate.expire("promo_item_stock_invalid_"+itemId,1,TimeUnit.MINUTES);
            return true;

        }else {
            //库存为负，此次交易失败，补回redis中库存
            //redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
            asynchronousIncreaseStock(itemId,amount);
            return false;
        }
    }


    @Override
    public boolean asynchronousIncreaseStock(Integer itemId, Integer amount) {
        redisTemplate.opsForValue().increment("promo_item_stock_" + itemId, amount.intValue());
        return true;
    }

    @Override
    @Transactional
    public void increaseSales(Integer itemId, Integer amount) {
        itemDOMapper.increaseSales(itemId,amount);
    }

    @Override
    public boolean asyncDecreaseStock(Integer itemId, Integer amount) {
        boolean mqResult = mqProducer.asyncReduceStock(itemId, amount);
        return mqResult;
    }


    /**用于商品活动缓存设置，因为生成订单需要两部分，第一部分需要验证商品，第二部分要验证用户*/
    @Override
    public ItemModel getItemByIdInCache(Integer id) {
        ItemModel itemModel = (ItemModel) redisTemplate.opsForValue().get("item_validate_" + id);
        if (itemModel == null) {
            itemModel = this.getItemById(id);
            redisTemplate.opsForValue().set("item_validate_" + id, itemModel);
            redisTemplate.expire("item_validate_" + id, 20, TimeUnit.SECONDS);
        }
        return itemModel;
    }

    /**初始化库存流水，本方法应该在MQ事务下单之前被orderController被调用*/
    @Override
    @Transactional
    public String initStockLog(Integer itemId, Integer amount) {
        StockLogDO stockLogDO = new StockLogDO();
        stockLogDO.setItemId(itemId);
        stockLogDO.setAmount(amount);
        stockLogDO.setStockLogId(UUID.randomUUID().toString().replace("-",""));
        //1表示初始状态，2表示下单扣减库存成功，3表示下单回滚
        stockLogDO.setStatus(1);
        stockLogDOMapper.insertSelective(stockLogDO);
        return stockLogDO.getStockLogId();
    }


    private ItemModel convertModelFromEntity(ItemDO itemDO, ItemStockDO itemStockDO){
        ItemModel itemModel = new ItemModel();
        BeanUtils.copyProperties(itemDO, itemModel);
        itemModel.setPrice(BigDecimal.valueOf(itemDO.getPrice()));
        itemModel.setStock(itemStockDO.getStock());

        return itemModel;
    }



}
